﻿#include "stdafx.h"
#if _QG_USE_GL
#include "qg_ogl_dev.h"
#include "qg_ogl_dev_ext.h"
#include "qg_ogl_mtrl.h"
#include "qg_ogl_draw.h"
#include "qg_trace.h"

using namespace _ogl;

// 레이아웃 올림
bool GLDevice::_CommitLayout(kint stride, kint count, kpointer elmsptr) throw()
{
#define BUFFER_OFFSET(offset)		((kbyte*)NULL + offset)
	GLLayout::Element* elms = (GLLayout::Element*)elmsptr;
	GLProgram* p = (GLProgram*)_sprn->m_shd;

	if (p)
	{
		// 세이더가 있을 때
		kint* pwcl = _vprn.ws_client;
		kint* pwcm = _vprn.ws_custom;

		for (kint i = 0; i < count; i++)
		{
			const GLLayout::Element& e = elms[i];
			GLProgram::AttribVar* pav = p->FindAttribUsage(e.usage, e.index);

			if (pav)
			{
				// 값 형식이 attribute로 존재한다
				GLuint attr = pav->offset;
				QglVertexAttribPointer(attr, e.size, e.format, e.normalized, stride, BUFFER_OFFSET(e.offset));
				QglEnableVertexArray(attr);
				*pwcm++ = attr;
			}
			else
			{
				// 고정형태로 존재한다
				switch (K_CAST_ENUM(e.usage))
				{
					case QGDLOU_POSITION:
					case QGDLOU_MAX_VALUE:	// DX9 POSITIONT
						// 꼭지점이 float이 아닌 경우가 있을까?!
						glVertexPointer(e.size, GL_FLOAT, stride, BUFFER_OFFSET(e.offset));
						glEnableClientState(GL_VERTEX_ARRAY);
						*pwcl++ = GL_VERTEX_ARRAY;
						break;

					case QGDLOU_COLOR:
						if (e.index == 0)
						{
							// DIFFUSE
							glColorPointer(e.size, e.format, stride, BUFFER_OFFSET(e.offset));
							glEnableClientState(GL_COLOR_ARRAY);
							*pwcl++ = GL_COLOR_ARRAY;
						}
						else if (e.index == 1)
						{
							// SPECULAR
							QglSecondaryColorPointer(e.size, e.format, stride, BUFFER_OFFSET(e.offset));
							glEnableClientState(GL_SECONDARY_COLOR_ARRAY);
							*pwcl++ = GL_SECONDARY_COLOR_ARRAY;
						}
						else
						{
							// 세번째는 뭐여!!!!
							// ...라고 하지만, 세이더가 아니면 쓰지 못함
							//return false;
						}
						break;

					case QGDLOU_NORMAL:
						glNormalPointer((GLenum)e.format, stride, BUFFER_OFFSET(e.offset));
						glEnableClientState(GL_NORMAL_ARRAY);
						*pwcl++ = GL_NORMAL_ARRAY;
						break;

					case QGDLOU_TEXTURE:
						if (_ActiveTexture(e.index))
						{
							glTexCoordPointer(e.size, e.format, stride, BUFFER_OFFSET(e.offset));
							glEnableClientState(GL_TEXTURE_COORD_ARRAY);
							*pwcl++ = 0xFFFF + e.index;
						}
						break;

					case QGDLOU_WEIGHT:
					case QGDLOU_INDEX:
					case QGDLOU_TANGENT:
					case QGDLOU_BINORMAL:
					default:
						// 다른건 처리할 수 없다
						break;
				}
			}
		}
	}
	else
	{
		// 세이더가 없을 때 -> 고정 그리기
		kint* pwcl = _vprn.ws_client;

		for (kint i = 0; i < count; i++)
		{
			const GLLayout::Element& e = elms[i];

			switch (K_CAST_ENUM(e.usage))
			{
				case QGDLOU_POSITION:
				case QGDLOU_MAX_VALUE:	// DX9 POSITIONT
					glVertexPointer(e.size, e.format, stride, BUFFER_OFFSET(e.offset));
					glEnableClientState(GL_VERTEX_ARRAY);
					*pwcl++ = GL_VERTEX_ARRAY;
					break;

				case QGDLOU_COLOR:
					if (e.index == 0)
					{
						// DIFFUSE
						glColorPointer(e.size, e.format, stride, BUFFER_OFFSET(e.offset));
						glEnableClientState(GL_COLOR_ARRAY);
						*pwcl++ = GL_COLOR_ARRAY;
					}
					else if (e.index == 1)
					{
						// SPECULAR
						QglSecondaryColorPointer(e.size, e.format, stride, BUFFER_OFFSET(e.offset));
						glEnableClientState(GL_SECONDARY_COLOR_ARRAY);
						*pwcl++ = GL_SECONDARY_COLOR_ARRAY;
					}
					else
					{
						// 세번째는 뭐여!!!!
						// ... 이놈도 세이더 없으면 못씀
						//return false;
					}
					break;

				case QGDLOU_NORMAL:
					glNormalPointer((GLenum)e.format, stride, BUFFER_OFFSET(e.offset));
					glEnableClientState(GL_NORMAL_ARRAY);
					*pwcl++ = GL_NORMAL_ARRAY;
					break;

				case QGDLOU_TEXTURE:
					if (_ActiveTexture(e.index))
					{
						glTexCoordPointer(e.size, e.format, stride, BUFFER_OFFSET(e.offset));
						glEnableClientState(GL_TEXTURE_COORD_ARRAY);
						*pwcl++ = 0xFFFF + e.index;
					}
					break;

				case QGDLOU_WEIGHT:
				case QGDLOU_INDEX:
				case QGDLOU_TANGENT:
				case QGDLOU_BINORMAL:
				default:
					// 세이더가 없으면 안된다
					break;
			}
		}
	}

	return true;
#undef BUFFER_OFFSET
}

// 그리기 매핑
kbool GLDevice::_BindDraw() throw()
{
	GLLayout* lo = (GLLayout*)_sprn->w_lo;

	// 꼭지점과 레이아웃
	for (kint s = 0; s < QGSGVERT_MAX_VALUE; s++)
	{
		kint stride = lo->_stage[s];

		if (_sprn->bd_vb[s])
		{
			_sprn->bd_vb[s] = false;

			GLBuffer* buf = (GLBuffer*)_sprn->w_vb[s];

			if (buf != NULL)
			{
				// 설정
				QglBindBuffer(GL_ARRAY_BUFFER, (GLuint)buf->GetDescription());
			}
			else if (stride > 0)
			{
				// 레이아웃에 자료는 있는데 버퍼가 없을 때만 오류 메시지 출력
				kobj_tracet(KLV_ERROR, KERR_ARGUMENT, "layout has vertex information. but, no vertex buffer found.");
			}
		}

		// 바인드는 해야함
		if (!_CommitLayout(stride, lo->_stcnt[s], lo->_stelm[s]))
			return false;
	}

#if 0
	// 인덱스 타입 때문에 Draw~()에서 직접 처리함
	if (bindindex)
	{
		// 인덱스 설정, 검사는 하지 않음
		GLBuffer* ib = (GLBuffer*)_sprn->w_ib;
		GLenum indextype = ib->GetStride() == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
		QglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)ib->GetDescription());
	}
#endif

	return true;
}

// 그리기 매핑 끔
bool GLDevice::_UnbindDraw() throw()
{
	for (ksize_t i = 0; i < K_COUNTOF(_vprn.ws_client); i++)
	{
		kint n = _vprn.ws_client[i];

		if (n < 0)
			break;

		// 설마 나중에 FFFF보다 높은 숫자가 나오진 않겠지
		// [2014-1-13 ksh] 현재 GL_TEXTURE_COORD_ARRAY=0x8078 
		if (n < 0xFFFF)
			glDisableClientState(n);
		else
		{
			if (_ActiveTexture(n - 0xFFFF))
				glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}

		_vprn.ws_client[i] = -1;
	}

	for (ksize_t i = 0; i < K_COUNTOF(_vprn.ws_custom); i++)
	{
		if (_vprn.ws_custom[i] < 0)
			break;

		QglDisableVertexArray(_vprn.ws_custom[i]);
		_vprn.ws_custom[i] = -1;
	}

	return true;
}

// 버퍼 만들기
qgBuffer* GLDevice::_RoamImplBuffer(const qgArprBuffer& ap)
{
	if (ap.type == QGBUFFER_INDEX || ap.type == QGBUFFER_VERTEX)
	{
		GLBuffer* buf = kobj_create(GLBuffer);

		if (!buf->_Create(ap, this))
		{
			buf->Unload();
			buf = NULL;
		}

		return buf;
	}
	else
	{
		// 인덱스도 아니고
		// 꼭지점도 아니면
		// 실패지 뭐...
		return NULL;
	}
}

// 레이아웃 만들기
qgLayout* GLDevice::_RoamImplLayout(qgLayoutData* data)
{
	GLLayout* lo = kobj_create(GLLayout);

	if (!lo->_Create(data))
	{
		lo->Unload();
		lo = NULL;
	}

	return lo;
}


//////////////////////////////////////////////////////////////////////////
// 버퍼
KIMPL_SUBCLASS(GLBuffer, "GLBuffer");

//
GLBuffer::GLBuffer() throw()
{
}

//
GLBuffer::~GLBuffer() throw()
{
	if (_node_desc)
	{
		if (_lock)
			GLBuffer::LeaveMap();

		GLuint bufid = (GLuint)_node_desc;
		QglDeleteBuffers(1, &bufid);
	}
}

//
bool GLBuffer::_Create(const qgArprBuffer& ap, GLDevice* dev)
{
	// 버퍼 만들고
	GLuint bufid;

	QglGenBuffers(1, &bufid);
	if (!bufid)
		return false;

	// 유형별 처리 및 버퍼 데이터 만들기
	// : STATIC		-> 한번 설정, 여러번 사용
	// : DYNAMIC	-> 여러번 설정, 여러번 사용
	// : STREAM		-> 한번 설정, 조금만 사용
	// : DRAW		-> 사용자가 설정, 그리기 위해 사용
	// : READ		-> GL이 읽음, 사용자 질의하기 위해 사용
	// : WRITE		-> GL이 읽음, 그리기 위해 사용
	GLenum gltype = ap.type == QGBUFFER_VERTEX ? GL_ARRAY_BUFFER : GL_ELEMENT_ARRAY_BUFFER;
	GLenum glusage;
	const GLvoid* ptr;
	GLsizeiptr size = ap.count * ap.stride;

	if (K_OFLAG(ap.flags, QGRESF_INIT))
	{
		glusage = GL_STATIC_DRAW;
		ptr = ap.data;
	}
	else
	{
		glusage = GL_DYNAMIC_DRAW;
		ptr = NULL;
	}

	QglBindBuffer(gltype, bufid);
	QglBufferData(gltype, size, ptr, glusage);

	//
	_gl_type = gltype;
	_gl_usage = glusage;
	_gl_size = size;

	_buftype = ap.type;
	_flags = ap.flags;
	_count = ap.count;
	_stride = ap.stride;

	_node_desc = (kuintptr)bufid;

	return true;
}

//
kpointer GLBuffer::EnterMap(kint flags)
{
	// 잠겨 있으면 실패
	if (_lock)
	{
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return NULL;
	}

	// INIT는 초기화에서만 가능
	if (K_OFLAG(_flags, QGRESF_INIT))
	{
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return NULL;
	}

	//
	QglBindBuffer(_gl_type, (GLuint)_node_desc);

	if (!K_OFLAG(flags, QGENTF_READ))
	{
		// 읽기가 없으면 버퍼 제거
		QglBufferData(_gl_type, _gl_size, NULL, _gl_usage);
	}

	//
	GLenum access;
	if (!K_OFLAG(flags, QGENTF_READ))
		access = GL_WRITE_ONLY;
	else
	{
		if (K_OFLAG(flags, QGENTF_WRITE))
			access = GL_READ_WRITE;
		else
			access = GL_READ_ONLY;
	}

	//
	_lock = QglMapBuffer(_gl_type, access);

	if (!_lock)
	{
		// 헐... 뭐징
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
	}

	return _lock;
}

//
bool GLBuffer::LeaveMap()
{
	if (!_lock)
	{
		// 잠겨있지 않음
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return false;
	}

	QglBindBuffer(_gl_type, (GLuint)_node_desc);

	if (!QglUnmapBuffer(_gl_type))
		kobj_tracep(KLV_ERROR, KERR_MAPPING);

	//
	_lock = NULL;

	return true;
}


//////////////////////////////////////////////////////////////////////////
// 레이아웃
KIMPL_SUBCLASS(GLLayout, "GLLayout");

//
GLLayout::GLLayout() throw()
{
}

//
GLLayout::~GLLayout() throw()
{
	for (kint i = 0; i < QGSGVERT_MAX_VALUE; i++)
	{
		if (_stelm[i])
			k_delete(_stelm[i]);
	}
}

//
bool GLLayout::_Create(qgLayoutData* data)
{
	// 루프
	kint i, cnt = data->elms.Count();
	kushort offset[QGSGVERT_MAX_VALUE] = {0, };
	kushort count[QGSGVERT_MAX_VALUE] = {0, };

	for (i = 0; i < cnt; i++)
	{
		qgLayoutElement& e = data->elms[i];
		kushort size = (kushort)qgResSupp::LayoutTypeToSize(e.type);
		kint slot = e.slot;

		// 추가 데이터 처리
		e.offset = offset[slot];
		e.size = size;

		// 오프셋 전진
		offset[slot] += size;
		count[slot]++;
	}

	// 스테이지별 보관
	Element* elms[QGSGVERT_MAX_VALUE] = {NULL, };

	for (i = 0; i < QGSGVERT_MAX_VALUE; i++)
	{
		if (count[i] != 0)
		{
			_stcnt[i] = count[i];
			elms[i] = _stelm[i] = k_new(count[i], Element);
		}
	}

	for (i = 0; i < cnt; i++)
	{
		const qgLayoutElement& e = data->elms[i];
		kint slot = e.slot;

		BakeElement(elms[slot]++, e);
	}

	// 키
	data->elms.Swap(_data.elms);

	// [2014-3-2 KIM] 값 
	for (_stride = i = 0; i < QGSGVERT_MAX_VALUE; i++)
	{
		_stride += offset[i];
		_stage[i] = offset[i];
	}

	return true;
}

//
void GLLayout::BakeElement(Element* u, const qgLayoutElement& e)
{
	static kint sSize[] =
	{
		1, 2, 3, 4,
		1, 2, 3, 4,
		2, 4,
		2, 4,
		2, 4,
		4
	};
	static GLenum sFormat[] =
	{
		GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT,
		GL_UNSIGNED_INT, GL_UNSIGNED_INT, GL_UNSIGNED_INT, GL_UNSIGNED_INT,
		GL_HALF_FLOAT, GL_HALF_FLOAT,
		GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE,
		GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT,
		GL_UNSIGNED_BYTE
	};
	static GLboolean sNormalized[] =
	{
		GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE,
		GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE,
		GL_FALSE, GL_FALSE,
		GL_FALSE, GL_FALSE,
		GL_FALSE, GL_FALSE,
		GL_TRUE
	};

	*(qgLayoutElement*)u = e;

	u->size = sSize[e.type];
	u->format = sFormat[e.type];
	u->normalized = sNormalized[e.type];
}

#endif	// _QG_USE_GL
